package com.wb.spring.boot.autoconfigure.client;

import com.wb.spring.boot.autoconfigure.core.WbContext;
import com.wb.spring.boot.autoconfigure.proxy.SimpleFeignJdkProxy;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.Optional;

/**
 * @author deeprado
 * @create 2023-03-01 8:12
 */
public class WbClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware, BeanFactoryAware {
    private static Log LOG = LogFactory.getLog(WbClientFactoryBean.class);
    private Class<?> type;
    private String name;
    private String url;
    private String contextId;
    private String path;

    private boolean async;

    private int maxAttempts;

    private boolean decode404;

    private boolean inheritParentContext = true;
    private ApplicationContext applicationContext;
    private BeanFactory beanFactory;
    private Class<?> fallback;
    private Class<?> fallbackFactory;
    private int readTimeoutMillis;
    private int connectTimeoutMillis;
    private boolean followRedirects;

    public WbClientFactoryBean() {

    }

    @Override
    public void afterPropertiesSet() {
        Assert.hasText(this.contextId, "Context id must be set");
        Assert.hasText(this.name, "Name must be set");
    }


    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    @Override
    public Object getObject() {
        return this.getTarget();
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
        this.beanFactory = applicationContext;
    }


    private String cleanPath() {
        String path = this.path.trim();
        if (StringUtils.hasLength(path)) {
            if (!path.startsWith("/")) {
                path = "/" + path;
            }
            if (path.endsWith("/")) {
                path = path.substring(0, path.length() - 1);
            }
        }
        return path;
    }

    @Override
    public Class<?> getObjectType() {
        return type;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    public Class<?> getType() {
        return type;
    }

    public void setType(Class<?> type) {
        this.type = type;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getContextId() {
        return contextId;
    }

    public void setContextId(String contextId) {
        this.contextId = contextId;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public boolean isAsync() {
        return async;
    }

    public int getMaxAttempts() {
        return maxAttempts;
    }

    public void setAsync(boolean async) {
        this.async = async;
    }

    public void setMaxAttempts(int maxAttempts) {
        this.maxAttempts = maxAttempts;
    }

    public boolean isInheritParentContext() {
        return inheritParentContext;
    }

    public void setInheritParentContext(boolean inheritParentContext) {
        this.inheritParentContext = inheritParentContext;
    }

    public ApplicationContext getApplicationContext() {
        return applicationContext;
    }


    public Class<?> getFallback() {
        return fallback;
    }

    public void setFallback(Class<?> fallback) {
        this.fallback = fallback;
    }

    public Class<?> getFallbackFactory() {
        return fallbackFactory;
    }

    public void setFallbackFactory(Class<?> fallbackFactory) {
        this.fallbackFactory = fallbackFactory;
    }

    public boolean isDecode404() {
        return decode404;
    }

    public void setDecode404(boolean decode404) {
        this.decode404 = decode404;
    }

    public BeanFactory getBeanFactory() {
        return beanFactory;
    }

    public <T> T getTarget() {

        String url = (Optional.ofNullable(this.url).isPresent())  ? this.url + cleanPath() : "";

        // 返回代理对象,先上个最简单的jdk代理吧
        return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{type}, new SimpleFeignJdkProxy(name, url, async, maxAttempts, beanFactory));
    }


    protected <T> T getOptional(WbContext context, Class<T> type) {
        return context.getInstance(contextId, type);
    }

    protected <T> T getInheritedAwareOptional(WbContext context, Class<T> type) {
        if (inheritParentContext) {
            return getOptional(context, type);
        } else {
            return context.getInstanceWithoutAncestors(contextId, type);
        }
    }


    protected <T> Map<String, T> getInheritedAwareInstances(WbContext context,
                                                            Class<T> type) {
        if (inheritParentContext) {
            return context.getInstances(contextId, type);
        } else {
            return context.getInstancesWithoutAncestors(contextId, type);
        }
    }

    private <T> T getOrInstantiate(Class<T> tClass) {
        try {
            return beanFactory != null ? beanFactory.getBean(tClass)
                    : applicationContext.getBean(tClass);
        } catch (NoSuchBeanDefinitionException e) {
            return BeanUtils.instantiateClass(tClass);
        }
    }

    protected <T> T get(WbContext context, Class<T> type) {
        T instance = context.getInstance(contextId, type);
        if (instance == null) {
            throw new IllegalStateException(
                    "No bean found of type " + type + " for " + contextId);
        }
        return instance;
    }

}
